﻿#include "udpcommu.h"
#include <array>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <thread>


#ifdef _WIN32
#  include <winsock2.h>
#  include <iphlpapi.h>
#  pragma comment(lib, "ws2_32.lib")
#  pragma comment(lib, "iphlpapi.lib")
#else
#  include <net/if.h>
#  include <sys/ioctl.h>
#  include <sys/socket.h>
#  include <unistd.h>
#endif
#ifdef _MSC_VER               // 只有 MSVC
#  if _MSC_VER >= 1900        // VS2015 及以上
#    pragma execution_character_set("utf-8")
#  endif
#endif


#define setFailedMsgAndReturnFalse(msg) \
    {                                   \
        mErrMsg = msg;                  \
        return false;                   \
    }

#define setErrnoMsgAndReturnFalse() \
    {                               \
        mErrMsg = strerror(errno);  \
        return false;               \
    }

#define setErrnoMsg() mErrMsg = strerror(errno);

UdpCommu::UdpCommu() {}

bool UdpCommu::bindTo(int port)
{
#ifdef _WIN32
	if (mSocket != INVALID_SOCKET) 
		setFailedMsgAndReturnFalse("已经初始化");
#else
	if (mSocket != -1)
		setFailedMsgAndReturnFalse("已经初始化");
#endif

    //创建socket
    mSocket = socket(PF_INET, SOCK_DGRAM, 0);
    if (mSocket == -1)
        setErrnoMsgAndReturnFalse();

    auto ret = -1;
    //允许广播
	int broadcast = 1;
#ifdef _WIN32
	auto* p_broadcast = (const char*)&broadcast;
#else
	const void* p_broadcast = &broadcast;
#endif
    ret = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, p_broadcast, sizeof(int));
    if (ret == -1)
        setErrnoMsgAndReturnFalse();

    //地址复用
    int reuse = 1;
#ifdef _WIN32
	auto* p_reuse = (const char*)&reuse;
#else
	const void* p_reuse = &reuse;
#endif
    ret = setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, p_reuse, sizeof(int));
    if (ret == -1)
        setErrnoMsgAndReturnFalse();

    //绑定
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);

    ret = ::bind(mSocket, (sockaddr *)&addr, sizeof(addr));
    if (ret == -1)
        setErrnoMsgAndReturnFalse();

    return true;
}

int UdpCommu::sentTo(const string &ip, int port, const void *data, int size)
{
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    addr.sin_port = htons(port);

#ifdef _WIN32
	auto ret = ::sendto(mSocket, static_cast<const char*>(data),
        size, 0, (sockaddr*)&addr, sizeof(addr));
#else
	auto ret = ::sendto(mSocket, data, size, 0, (sockaddr*)&addr, sizeof(addr));

#endif

    if (ret == -1)
        setErrnoMsg();

    return ret;
}

bool UdpCommu::startAsyncRecv(UdpRecvHandler handler)
{
    if (handler == nullptr)
        setFailedMsgAndReturnFalse("handler不能为空")

            if (mSocket == -1)
                setFailedMsgAndReturnFalse("请先初始化socket");

    mRecvHandler = handler;
    if (!mAsyncMode)
    {
        mAsyncMode = true;
        std::thread t(&UdpCommu::recvThread, this);
        t.detach();
    }

    return true;
}

void UdpCommu::close()
{
    if (mSocket == -1)
        return;

#ifdef _WIN32
    ::closesocket(mSocket);
#else
    ::close(mSocket);
#endif
   
    mSocket = -1;
    mAsyncMode = false;
}
#ifdef _WIN32
std::string get_mac_addr()
{
	ULONG bufLen = 0;
	// 先拿缓冲区大小
	if (GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, nullptr, &bufLen) != ERROR_BUFFER_OVERFLOW)
		return "";

	std::vector<uint8_t> buffer(bufLen);
	auto pAdapters = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());

	if (GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, pAdapters, &bufLen) != ERROR_SUCCESS)
		return "";

	// 取第一个启用的物理网卡
	for (auto p = pAdapters; p; p = p->Next) {
		if (p->PhysicalAddressLength == 0 ||
			p->IfType != IF_TYPE_ETHERNET_CSMACD)   // 以太网
			continue;

		char mac[18]{};
		snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
			p->PhysicalAddress[0], p->PhysicalAddress[1],
			p->PhysicalAddress[2], p->PhysicalAddress[3],
			p->PhysicalAddress[4], p->PhysicalAddress[5]);
		return mac;
	}

	return "";
}

#else
string get_mac_addr()
{
	const int MAC_SIZE = 18;
	struct ifreq ifr;
	int sd;
	const char eth_inf[] = "eth0";
	char mac[MAC_SIZE] = "";

	bzero(&ifr, sizeof(struct ifreq));
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		printf("get %s mac address socket creat error\n", eth_inf);
		return "";
	}

	strncpy(ifr.ifr_name, eth_inf, sizeof(ifr.ifr_name) - 1);

	if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
	{
		printf("get %s mac address error\n", eth_inf);
		close(sd);
		return "";
	}

	snprintf(mac, MAC_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x",
		(unsigned char)ifr.ifr_hwaddr.sa_data[0],
		(unsigned char)ifr.ifr_hwaddr.sa_data[1],
		(unsigned char)ifr.ifr_hwaddr.sa_data[2],
		(unsigned char)ifr.ifr_hwaddr.sa_data[3],
		(unsigned char)ifr.ifr_hwaddr.sa_data[4],
		(unsigned char)ifr.ifr_hwaddr.sa_data[5]);

	close(sd);
	return mac;
}
#endif
string UdpCommu::getBoundMac()
{
    return get_mac_addr();
}

string UdpCommu::getErrMsg()
{
    return mErrMsg;
}

//void UdpCommu::recvThread()
//{
//    timeval timeo = {3, 0};
//    auto ret = setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeval));
//    if (ret != 0)
//    {
//        printf("faield to set recv timeo\n");
//        mAsyncMode = false;
//        return;
//    }
//
//    std::array<char, MAX_RCV_SIZE> buf;
//    sockaddr_in addr;
//    socklen_t len = sizeof(addr);
//
//    while (mSocket != -1)
//    {
//        buf.fill(0);
//        memset(&addr, 0, len);
//
//        auto size = recvfrom(mSocket, buf.data(), MAX_RCV_SIZE, 0, (sockaddr *)&addr, &len);
//        if (size < 0)
//        {
//            if (errno == EAGAIN || errno == ETIMEDOUT)
//                continue;
//
//            printf("error occur:%s\n", strerror(errno));
//            break;
//        }
//
//        auto ip = inet_ntoa(addr.sin_addr);
//        vector<char> data(std::begin(buf), std::begin(buf) + size);
//        mRecvHandler(ip, data);
//    }
//
//    printf("end recv thread\n");
//    mAsyncMode = false;
//}
void UdpCommu::recvThread()
{
#ifdef _WIN32
	// Windows 用 DWORD 毫秒数
	DWORD timeout = 3000;
	auto ret = setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO,
		(const char*)&timeout, sizeof(timeout));
#else
	timeval timeo{ 3, 0 };
	auto ret = setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO,
		&timeo, sizeof(timeo));
#endif
	if (ret != 0)
	{
		printf("failed to set recv timeout\n");
		mAsyncMode = false;
		return;
	}

	std::array<char, MAX_RCV_SIZE> buf;
	sockaddr_in addr{};
#ifdef _WIN32
	int len = sizeof(addr);   // Windows 没有 socklen_t
#else
	socklen_t len = sizeof(addr);
#endif

	while (mSocket != -1)
	{
		buf.fill(0);

		auto size = recvfrom(mSocket, buf.data(), static_cast<int>(buf.size()),
			0, (sockaddr*)&addr, &len);
		if (size < 0)
		{
#ifdef _WIN32
			int err = WSAGetLastError();
			if (err == WSAETIMEDOUT || err == WSAEWOULDBLOCK)
				continue;
			printf("error occur:%d\n", err);
#else
			if (errno == EAGAIN || errno == ETIMEDOUT)
				continue;
			printf("error occur:%s\n", strerror(errno));
#endif
			break;
		}

		auto ip = inet_ntoa(addr.sin_addr);
		std::vector<char> data(buf.begin(), buf.begin() + size);
		mRecvHandler(ip, data);
	}

	printf("end recv thread\n");
	mAsyncMode = false;
}
